﻿/*
 * LINQ to LDAP
 * http://linqtoldap.codeplex.com/
 * 
 * Copyright Alan Hatter (C) 2010-2012
 * alan.hatter@gmail.com - http://hattercoding.blogspot.com/
 * 
 * This project is subject to licensing restrictions. Visit http://linqtoldap.codeplex.com/license for more information.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using LinqToLdap.Exceptions;
using LinqToLdap.Mapping;
using LinqToLdap.QueryCommands;
using LinqToLdap.QueryCommands.Options;

namespace LinqToLdap.Visitors
{
    internal class QueryTranslator : ExpressionVisitor
    {
        private StringBuilder _sb;
        private bool _not;
        private Stack<ExpressionType> _expressionStack;
        private int _exclusiveWhereCount;
        private bool _dnFilter;
        private readonly IObjectMapping _mapping;
        private bool _binaryExpression;
        private bool _ignoreOc;
        private int _customFiltersCount;
        private StringBuilder _sbCustomFilter;
        private PagingOptions _pagingOptions;
        private SortingOptions _sortingOptions;
        private int? _toListPageSize;
        private int? _takeSize;
        private IPropertyMapping _currentProperty;
        private List<DirectoryControl> _controls;
        private bool _falseEncountered;

        private QueryCommandType _commandType = QueryCommandType.StandardCommand;
        private QueryCommandOptions _commandOptions;

        public QueryTranslator(IObjectMapping mapping)
        {
            _mapping = mapping;
        }

        public bool IsDynamic { get; set; }

#pragma warning disable 649
        private IQueryCommandFactory _factory;
#pragma warning restore 649
        private IQueryCommandFactory QueryCommandFactory
        {
            get { return _factory ?? new QueryCommandFactory(); }
        }

        public IQueryCommand Translate(Expression expression)
        {
            expression = Evaluator.PartialEval(expression);
            _sb = new StringBuilder();
            _sbCustomFilter = new StringBuilder();
            _expressionStack = new Stack<ExpressionType>();
            Visit(expression);

            if (_commandOptions == null)
            {
                if (IsDynamic)
                {
                    _commandOptions = new DynamicQueryCommandOptions();
                }
                else
                {
                    _commandOptions = new StandardQueryCommandOptions(_mapping, _mapping.Properties);
                }
            }

            if ((_falseEncountered && _sb.Length == 0) == false)
            {
                string filter;
                string simpleFilter = null;
                string ocFilter = _ignoreOc ? null : GetOcFilter();
                if (_sb.Length == 0)
                {
                    filter = ocFilter;
                }
                else
                {
                    if (ocFilter != null)
                    {
                        simpleFilter = _sb.ToString();

                        filter = string.Format("(&{0}{1})", ocFilter, simpleFilter);
                    }
                    else
                    {
                        filter = simpleFilter = _exclusiveWhereCount > 1
                                     ? string.Format("(&{0})", _sb)
                                     : string.Format("{0}", _sb);
                    }
                }

                if (_customFiltersCount > 0)
                {
                    var customFilter = _customFiltersCount > 1
                                              ? string.Format("(&{0})", _sbCustomFilter)
                                              : _sbCustomFilter.ToString();

                    if (filter.IsNullOrEmpty())
                    {
                        filter = simpleFilter = customFilter;
                    }
                    else
                    {
                        filter = string.Format("(&{0}{1})", customFilter, filter);
                        simpleFilter = string.Format("(&{0}{1})", customFilter, simpleFilter);
                    }
                }

                _commandOptions.Filter = filter;
                _commandOptions.FilterWithoutOc = simpleFilter;
            }
            else
            {
                _commandOptions.YieldNoResults = true;
            }

            _commandOptions.PagingOptions = _pagingOptions;
            _commandOptions.SortingOptions = _sortingOptions;
            _commandOptions.PageSize = _toListPageSize;
            _commandOptions.TakeSize = _takeSize;
            _commandOptions.Controls = _controls;

            var queryCommand = QueryCommandFactory.GetCommand(_commandType, _commandOptions, _mapping);
            return queryCommand;
        }

        private string GetOcFilter()
        {
            if (!_mapping.ObjectCategory.IsNullOrEmpty())
            {
                return string.Format("(objectCategory={0})", _mapping.ObjectCategory);
            }

            if (this._mapping.ObjectClasses == null || !this._mapping.ObjectClasses.Any())
            {
                return null;
            }

            var objectClassFilters = string.Join(
                string.Empty,
                _mapping.ObjectClasses.Select(oc => string.Format("(objectClass={0})", oc)));

            return _sb.Length == 0 && _customFiltersCount == 0
                       ? (_mapping.ObjectClasses.Count() > 1
                              ? string.Format("(&{0})", objectClassFilters)
                              : objectClassFilters)
                       : objectClassFilters;
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression) e).Operand;
            }
            return e;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof (Queryable) || m.Method.DeclaringType == typeof (QueryableExtensions))
            {
                switch (m.Method.Name)
                {
                    case "Where":
                        _exclusiveWhereCount++;
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.StandardCommand;
                        break;
                    case "Any":
                        if (m.Arguments.Count > 1) _exclusiveWhereCount++;
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.AnyCommand;
                        break;
                    case "FirstOrDefault":
                    case "First":
                        if (m.Arguments.Count > 1) _exclusiveWhereCount++;
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.FirstOrDefaultCommand;
                        break;
                    case "SingleOrDefault":
                        if (m.Arguments.Count > 1) _exclusiveWhereCount++;
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.SingleOrDefaultCommand;
                        break;
                    case "Single":
                        if (m.Arguments.Count > 1) _exclusiveWhereCount++;
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.SingleCommand;
                        break;
                    case "Count":
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.CountCommand;
                        break;
                    case "ListAttributes":
                        Dictionary<string, string> attributes = null;
                        foreach (Expression t in m.Arguments)
                        {
                            if (t.Type == typeof(string[]))
                            {
                                attributes = (((ConstantExpression)t).Value as string[]).ToDictionary(s => s);
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _commandOptions = new ListAttributesQueryCommandOptions(attributes);
                        _commandType = QueryCommandType.ListAttributesCommand;
                        break;
                    case "FilterWith":
                        foreach (Expression t in m.Arguments)
                        {
                            if (t.Type == typeof(string))
                            {
                                _sbCustomFilter.Append(((ConstantExpression)t).Value.ToString());
                                _customFiltersCount++;
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _commandType = QueryCommandType.StandardCommand;
                        break;
                    case "Select":
                        foreach (Expression t in m.Arguments)
                        {
                            var lambda = StripQuotes(t) as LambdaExpression;
                            if (lambda != null)
                            {
                                if (_commandOptions != null) throw new FilterException("Cannot have multiple Select projections.");
                                
                                if (IsDynamic)
                                {
                                    var projection = new DynamicSelectProjector(_mapping.Properties)
                                        .ProjectProperties(lambda);
                                    
                                    _commandOptions = new DynamicQueryCommandOptions(projection);
                                }
                                else
                                {
                                    var projection = new SelectProjector(_mapping.Properties)
                                        .ProjectProperties(lambda);
                                    _commandOptions = new ProjectionQueryCommandOptions(_mapping, projection);
                                }
                            }
                            else if (t.Type == typeof(string[]))
                            {
                                if (!IsDynamic)
                                    throw new FilterException(
                                        "Cannot use a string attribute projection with a static type.");
                                if (_commandOptions != null) throw new FilterException("Cannot have multiple Select projections.");
                                _commandOptions =
                                    new DynamicQueryCommandOptions(((ConstantExpression) t).Value as string[]);
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _commandType = QueryCommandType.StandardCommand;
                        break;
                    case "ToPage":
                        int pageSize = 0;
                        byte[] nextPage = null;
                        foreach (Expression t in m.Arguments)
                        {
                            if (t.Type == typeof(int))
                            {
                                pageSize = (int)((ConstantExpression)t).Value;
                            }
                            else if (t.Type == typeof(byte[]))
                            {
                                nextPage = (byte[])((ConstantExpression)t).Value;
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _pagingOptions = new PagingOptions(pageSize, nextPage);
                        break;
                    case "InPagesOf":
                        foreach (Expression t in m.Arguments)
                        {
                            if (t.Type == typeof(int))
                            {
                                _toListPageSize = (int)((ConstantExpression)t).Value;
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _commandType = QueryCommandType.StandardCommand;
                        break;
                    case "ToList":
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _commandType = QueryCommandType.StandardCommand;
                        break;
                    case "Take":
                        int takeAmount = 0;
                        foreach (Expression t in m.Arguments)
                        {
                            if (t.Type == typeof(int))
                            {
                                takeAmount = (int)((ConstantExpression)t).Value;
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _takeSize = takeAmount;
                        break;
                    case "WithControls":
                       if (_controls == null) _controls = new List<DirectoryControl>();
                        foreach (Expression t in m.Arguments)
                        {
                            IEnumerable<DirectoryControl> controls;
                            if (t is ConstantExpression && (controls = ((ConstantExpression)t).Value as IEnumerable<DirectoryControl>) != null)
                            {
                                _controls.AddRange(controls);
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        break;
                    case "OrderByDescending":
                        string descRule = null;
                        foreach (Expression t in m.Arguments)
                        {
                            var lambda = StripQuotes(t) as LambdaExpression;
                            if (lambda != null)
                            {
                                var attribute = GetMemberName((MemberExpression)lambda.Body);
                                _sortingOptions = new SortingOptions(attribute, true);
                            }
                            else if (t.Type == typeof(string))
                            {
                                descRule = ((ConstantExpression)t).Value.ToString();
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _sortingOptions.SetMatchingRule(descRule);
                        break;
                    case "OrderBy":
                        string ascRule = null;
                        foreach (Expression t in m.Arguments)
                        {
                            var lambda = StripQuotes(t) as LambdaExpression;
                            if (lambda != null)
                            {
                                var attribute = GetMemberName((MemberExpression)lambda.Body);
                                _sortingOptions = new SortingOptions(attribute, false);
                            }
                            else if (t.Type == typeof(string))
                            {
                                ascRule = ((ConstantExpression)t).Value.ToString();
                            }
                            else
                            {
                                Visit(t);
                            }
                        }
                        _sortingOptions.SetMatchingRule(ascRule);
                        break;
                    case "IgnoreOC":
                        foreach (Expression t in m.Arguments)
                        {
                            Visit(t);
                        }
                        _ignoreOc = true;
                        break;
                }
            }
            else if (m.Method.DeclaringType == typeof (Filter))
            {
                switch (m.Method.Name)
                {
                    case "Approximately":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("~=");
                        Visit(m.Arguments[2]);
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "Equal":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("=");
                        Visit(m.Arguments[2]);
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "MatchingRule":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append(":");
                        Visit(m.Arguments[2]);
                        _sb.Append(":=");
                        Visit(m.Arguments[3]);
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "EqualAnything":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("=");
                        _sb.Append("*");
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "EqualAny":
                        var values = ((ConstantExpression) m.Arguments[2]).Value as IEnumerable<string>;

                        if (values == null || !values.Any())
                        {
                            throw new FilterException("Cannot create an EqualAny filter with null or empty values.");
                        }
                        int count = values.Count();
                        if (count > 1)
                        {
                            _sb.Append("(|");
                        }
                        _dnFilter = true;
                        foreach (var value in values)
                        {
                            _sb.Append("(");
                            if (_not) _sb.Append("!(");
                            Visit(m.Arguments[1]);
                            _sb.Append("=");
                            Visit(Expression.Constant(value));
                            if (_not) _sb.Append(")");
                            _sb.Append(")");
                        }
                        _dnFilter = false;
                        if (count > 1)
                        {
                            _sb.Append(")");
                        }
                        break;
                    case "StartsWith":
                        _sb.Append("(");
                         if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("=");
                        Visit(m.Arguments[2]);
                        _sb.Append("*");
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "EndsWith":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("=");
                        _sb.Append("*");
                        Visit(m.Arguments[2]);
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "Like":
                        _sb.Append("(");
                        if (_not) _sb.Append("!(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append("=");
                        _sb.Append("*");
                        Visit(m.Arguments[2]);
                        _sb.Append("*");
                        _dnFilter = false;
                        if (_not) _sb.Append(")");
                        _sb.Append(")");
                        break;
                    case "GreaterThanOrEqual":
                        _sb.Append("(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append(_not ? "<=" : ">=");
                        Visit(m.Arguments[2]);
                        _dnFilter = false;
                        _sb.Append(")");
                        break;
                    case "GreaterThan":
                        if (_not)
                        {
                            _sb.Append("(");
                            _dnFilter = true;
                            Visit(m.Arguments[1]);
                            _sb.Append("<=");
                            Visit(m.Arguments[2]);
                            _dnFilter = false;
                            _sb.Append(")");
                        }
                        else
                        {
                            _sb.Append("(&(");
                            _dnFilter = true;
                            Visit(m.Arguments[1]);
                            _sb.Append(">=");
                            Visit(m.Arguments[2]);
                            _sb.Append(")(!(");
                            Visit(m.Arguments[1]);
                            _sb.Append("=");
                            Visit(m.Arguments[2]);
                            _dnFilter = false;
                            _sb.Append(")))");
                        }
                        break;
                    case "LessThanOrEqual":
                        _sb.Append("(");
                        _dnFilter = true;
                        Visit(m.Arguments[1]);
                        _sb.Append(_not ? ">=" : "<=");
                        Visit(m.Arguments[2]);
                        _dnFilter = false;
                        _sb.Append(")");
                        break;
                    case "LessThan":
                        if (_not)
                        {
                            _sb.Append("(");
                            _dnFilter = true;
                            Visit(m.Arguments[1]);
                            _sb.Append(">=");
                            Visit(m.Arguments[2]);
                            _dnFilter = false;
                            _sb.Append(")");
                        }
                        else
                        {
                            _sb.Append("(&(");
                            _dnFilter = true;
                            Visit(m.Arguments[1]);
                            _sb.Append("<=");
                            Visit(m.Arguments[2]);
                            _sb.Append(")(!(");
                            Visit(m.Arguments[1]);
                            _sb.Append("=");
                            Visit(m.Arguments[2]);
                            _dnFilter = false;
                            _sb.Append(")))");
                        }
                        break;
                }
            }
            else if (m.Method.DeclaringType == typeof (string))
            {
                _sb.Append("(");
                switch (m.Method.Name)
                {
                    case "Contains":
                        if (_not) _sb.Append("!(");
                        Visit(m.Object);
                        _sb.Append("=*");
                        Visit(m.Arguments[0]);
                        _sb.Append("*");
                        if (_not) _sb.Append(")");
                        break;
                    case "StartsWith":
                        if (_not) _sb.Append("!(");
                        Visit(m.Object);
                        _sb.Append("=");
                        Visit(m.Arguments[0]);
                        _sb.Append("*");
                        if (_not) _sb.Append(")");
                        break;
                    case "EndsWith":
                        if (_not) _sb.Append("!(");
                        Visit(m.Object);
                        _sb.Append("=*");
                        Visit(m.Arguments[0]);
                        if (_not) _sb.Append(")");
                        break;
                    case "Equals":
                        if (_not) _sb.Append("!(");
                        Visit(m.Object);
                        _sb.Append("=");
                        Visit(m.Arguments[0]);
                        if (_not) _sb.Append(")");
                        break;
                    default:
                        throw new NotSupportedException("String method " + m.Method.Name + " is not supported.");
                }
                _sb.Append(")");
            }
            else if (m.Method.DeclaringType == typeof (Enumerable) ||
                     m.Method.DeclaringType.GetInterface("IEnumerable`1") != null)
            {
                if (m.Method.Name.Equals("Contains"))
                {
                    if (m.Arguments.Count == 1)
                    {
                        if (m.Object is MemberExpression)
                        {
                            _sb.Append("(");
                            if (_not) _sb.Append("!(");
                            Visit(m.Object);
                            _sb.Append("=");
                            Visit(m.Arguments[0]);
                            if (_not) _sb.Append(")");
                            _sb.Append(")");
                        }
                        else
                        {
                            VisitArgumentsForContains(m);
                        }
                    }
                    else
                    {
                        if (m.Arguments[0] is MemberExpression)
                        {
                            _sb.Append("(");
                            if (_not) _sb.Append("!(");
                            Visit(m.Arguments[0]);
                            _sb.Append("=");
                            Visit(m.Arguments[1]);
                            if (_not) _sb.Append(")");
                            _sb.Append(")");
                        }
                        else
                        {
                            VisitArgumentsForContains(m);
                        }
                    }
                }
                else
                {
                    throw new NotSupportedException("Collection method " + m.Method.Name + " is not supported.");
                }
            }
            else
            {
                throw new NotSupportedException("Method " + m.Method.Name + " on type " + m.Method.DeclaringType.FullName + " could not be translated.");
            }
            return m;
        }

        private void VisitArgumentsForContains(MethodCallExpression m)
        {
            IEnumerable list;
            Expression member;
            var enumerableVisitor = new EnumerableVisitor();
            var memberVisitor = new MemberVisitor();

            if (m.Arguments.Count == 1)
            {
                list = enumerableVisitor.GetList(m.Object);
                member = memberVisitor.GetMember(m.Arguments[0]);
            }
            else
            {
                list = enumerableVisitor.GetList(m.Arguments[0]);
                member = memberVisitor.GetMember(m.Arguments[1]);
            }

            if (list == null || (member as MemberExpression) == null)
            {
                throw new FilterException("Could not translate Contains.");
            }

            _sb.Append("(|");
            foreach (object value in list)
            {
                Visit(Expression.Equal(member, Expression.Constant(value)));
            }
            _sb.Append(")");
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.NodeType)
            {
                case ExpressionType.Not:
                    _not = true;
                    Visit(u.Operand);
                    _not = false;
                    break;
                case ExpressionType.Quote:
                    Visit(!_dnFilter ? StripQuotes(u) : u.Operand);
                    break;
                case ExpressionType.Convert:
                    Visit(u.Operand);
                    break;
                default:
                    throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported",
                                                                  u.NodeType));
            }
            return u;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            bool rightIsNull = new NullVisitor().IsNull(b.Right);
            _binaryExpression = true;
            switch (b.NodeType)
            {
                case ExpressionType.AndAlso:
                    if (_expressionStack.Count == 0 || _expressionStack.Peek() != ExpressionType.AndAlso) _sb.Append("(&");
                    _expressionStack.Push(ExpressionType.AndAlso);
                    Visit(b.Left);
                    Visit(b.Right);
                    _expressionStack.Pop();
                    if (_expressionStack.Count == 0 || _expressionStack.Peek() != ExpressionType.AndAlso) _sb.Append(")");
                    break;
                case ExpressionType.OrElse:
                    if (_expressionStack.Count == 0 || _expressionStack.Peek() != ExpressionType.OrElse) _sb.Append("(|");
                    _expressionStack.Push(ExpressionType.OrElse);
                    Visit(b.Left);
                    Visit(b.Right);
                    _expressionStack.Pop();
                    if (_expressionStack.Count == 0 || _expressionStack.Peek() != ExpressionType.OrElse) _sb.Append(")");
                    break;
                case ExpressionType.Equal:
                    _sb.Append("(");
                    if (rightIsNull)
                    {
                        if (!_not) _sb.Append("!(");
                    }
                    else
                    {
                        if (_not) _sb.Append("!(");
                    }
                    Visit(b.Left);
                    _sb.Append("=");
                    Visit(b.Right);
                    if (rightIsNull)
                    {
                        if (!_not) _sb.Append(")");
                    }
                    else
                    {
                        if (_not) _sb.Append(")");
                    }
                    _sb.Append(")");
                    break;
                case ExpressionType.NotEqual:
                    _sb.Append("(");
                    if (rightIsNull)
                    {
                        if (_not) _sb.Append("!(");
                    }
                    else
                    {
                        if (!_not) _sb.Append("!(");
                    }
                    Visit(b.Left);
                    _sb.Append("=");
                    Visit(b.Right);
                    if (rightIsNull)
                    {
                        if (_not) _sb.Append(")");
                    }
                    else
                    {
                        if (!_not) _sb.Append(")");
                    }
                    _sb.Append(")");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    _sb.Append("(");
                    Visit(b.Left);
                    _sb.Append(_not ? "<=" : ">=");
                    Visit(b.Right);
                    _sb.Append(")");
                    break;
                case ExpressionType.GreaterThan:
                    Expression greaterThanLeft = Expression.GreaterThanOrEqual(b.Left, b.Right);
                    Expression greaterThanRight = Expression.Not(Expression.Equal(b.Left, b.Right));
                    Visit(Expression.AndAlso(greaterThanLeft, greaterThanRight));
                    break;
                case ExpressionType.LessThanOrEqual:
                    _sb.Append("(");
                    Visit(b.Left);
                    _sb.Append(_not ? ">=" : "<=");
                    Visit(b.Right);
                    _sb.Append(")");
                    break;
                case ExpressionType.LessThan:
                    Expression lessThanLeft = Expression.LessThanOrEqual(b.Left, b.Right);
                    Expression lessThanRight = Expression.Not(Expression.Equal(b.Left, b.Right));
                    Visit(Expression.AndAlso(lessThanLeft, lessThanRight));
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported",
                                                                  b.NodeType));
            }
            _binaryExpression = false;
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            var q = c.Value as IQueryable;
            if (q != null)
            {
            }
            else if (c.Value == null || string.Empty.Equals(c.Value))
            {
                _sb.Append(_dnFilter ? "" : "*");
            }
            else if (c.Value is Expression)
            {
                Visit(c.Value as Expression);
            }
            else if (_dnFilter)
            {
                _sb.Append((c.Value as string));
            }
            else if (_currentProperty != null)
            {
                var str = _currentProperty.FormatValueToFilter(c.Value);

                if (str.IsNullOrEmpty())
                {
                    throw new FilterException("Cannot use white spaces for a value in a filter.");
                }

                _sb.Append(str);
            }
            else if (!_binaryExpression && false.Equals(c.Value))
            {
                _falseEncountered = true;
            }
            return c;
        }

        protected override Expression VisitInvocation(InvocationExpression iv)
        {
            // ReSharper disable PossibleNullReferenceException
            return Visit((iv.Expression as LambdaExpression).Body);
            // ReSharper restore PossibleNullReferenceException
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            var isHasValue = false;
            MemberExpression copy;
            if (m.Expression.NodeType != ExpressionType.Parameter)
            {
                copy = m.Expression as MemberExpression;
                if (copy == null || copy.Expression.NodeType != ExpressionType.Parameter)
                {
                    copy = m;
                }
                else
                {
                    isHasValue = m.Member.Name == "HasValue" && m.Member.DeclaringType.Name == "Nullable`1";
                }
            }
            else
            {
                copy = m;
            }

            if (copy.Expression.NodeType == ExpressionType.Parameter)
            {
                if (!_binaryExpression && isHasValue)
                {
                    Visit(Expression.NotEqual(copy, Expression.Constant(null)));
                }
                else if ((!_binaryExpression && copy.Type == typeof(bool)))
                {
                    ConstantExpression constat = _not ? Expression.Constant(false) : Expression.Constant(true);
                    bool reverseNegation = false;
                    if (_not)
                    {
                        reverseNegation = true;
                        _not = false;
                    }
                    Visit(Expression.Equal(copy, constat));
                    if (reverseNegation)
                    {
                        _not = true;
                    }
                }
                else
                {
                    _currentProperty = _mapping.GetPropertyMapping(copy.Member.Name);
                    _sb.Append(_currentProperty.AttributeName);
                }
                return m;
            }
            throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
        }

        private string GetMemberName(MemberExpression m)
        {
            string attributeName;
            if (!_mapping.Properties.TryGetValue(m.Member.Name, out attributeName))
            {
                throw new MappingException(string.Format("{0} was not found as a mapped property on {1}",
                                                         m.Member.Name, _mapping.Type.FullName));
            }

            return attributeName;
        }
    }
}
